﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Jemgine.Engine.Renderer
{
    public enum ShadowType
    {
        Solid,
        None,
    }

    public class Driver
    {
        private RenderTarget2D ShadowBuffer;
        private RenderTarget2D BackgroundLightBuffer;
        private RenderTarget2D MiddlegroundLightBuffer;
        private Texture2D LightTexture;

        public Driver(GraphicsDevice Device, EpisodeContentManager Content)
        {
            ShadowBuffer = new RenderTarget2D(Device, 512, 512, false,
                SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents);
            BackgroundLightBuffer = new RenderTarget2D(Device, 256, 256, false,
                SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents);
            MiddlegroundLightBuffer = new RenderTarget2D(Device, 128, 128, false,
                SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents);
            LightTexture = Content.Load<Texture2D>("light");
        }

        public void DrawMap(List<Data.EntityRecord> Elements, Data.MapHeader Header, GraphicsDevice Device, Camera Cam,
            RenderEffect Effect, VertexPusher Pusher, EpisodeContentManager Content, bool DisplayShadowmap = false)
        {
            var Viewport = Device.Viewport;
            Pusher.Effect = Effect;

            Effect.Tint = Vector4.One;
            Effect.RestoreMatrixStack();
            Device.SetRenderTarget(BackgroundLightBuffer);
            Effect.ProjectionMatrix = Cam.Projection;
            Effect.ViewMatrix = Cam.View;
            Effect.Color = Vector4.One;
            Device.Clear(ClearOptions.Target, new Vector4(Header.AmbientLighting, 1.0f), 0, 0);

            foreach (var Entity in Elements)
            {
                foreach (Data.Light Light in from Element in Entity.AttachedElements 
                                             where Element is Data.Light 
                                             select Element as Data.Light)
                {
                    if (!Light.Enabled) continue;
                    Device.SetRenderTarget(ShadowBuffer);
                    Effect.ProjectionMatrix = Cam.Projection;
                    Effect.ViewMatrix = Cam.View;
                    Effect.Color = Vector4.One;
                    Effect.Tint = Vector4.One;

                    Device.Clear(Microsoft.Xna.Framework.Color.Black);
                    Effect.Technique = "DrawShadow";

                    foreach (var EntityB in from MapElement in Elements
                                            where MapElement.DrawDepth == Data.EntityRecord._DrawDepth.MiddleGround
                                            select MapElement)
                        foreach (var ShadowCaster in from Element in EntityB.AttachedElements
                                                     where Element is ICastShadow
                                                     select Element as Data.EntityElement)
                        {
                            var M = (ShadowCaster.BoneReference == null ? Matrix.Identity : ShadowCaster.BoneReference.Transform) *
                                EntityB.GetTransform();
                            (ShadowCaster as ICastShadow).DrawShadow(Effect, Light.GetAnimatedWorldPosition(), M, Pusher);
                        }

                    Device.SetRenderTarget(BackgroundLightBuffer);
                    Effect.ProjectionMatrix = Cam.Projection;
                    Effect.ViewMatrix = Cam.View;
                    Effect.Color = new Vector4(Light.Color, 1.0f);
                    Effect.Tint = Vector4.One;

                    Effect.ShadowMap = ShadowBuffer;
                    Effect.PushMatrix(Light.GetFullTransform());
                    Effect.PushMatrix(Matrix.CreateTranslation(new Vector3(Light.Position, 0.0f)));
                    Effect.PushMatrix(Matrix.CreateScale(Light.Radius * 2));

                    Effect.Texture = LightTexture;
                    Effect.Technique = "DrawLight";
                    Pusher.DrawSprite(false);

                    Effect.PopMatrix();
                    Effect.PopMatrix();
                    Effect.PopMatrix();
                }
            }

            Device.SetRenderTarget(MiddlegroundLightBuffer);
            Device.Clear(ClearOptions.Target, new Vector4(Header.AmbientLighting, 1.0f), 0, 0);
            Effect.ProjectionMatrix = Cam.Projection;
            Effect.ViewMatrix = Cam.View;
            Effect.Tint = Vector4.One;
            Effect.Texture = LightTexture;
            Effect.Technique = "DrawLight2";

            foreach (var Entity in Elements)
            {
                foreach (Data.Light Light in from Element in Entity.AttachedElements
                                             where Element is Data.Light
                                             select Element as Data.Light)
                {
                    if (!Light.Enabled) continue;

                    Effect.Color = new Vector4(Light.Color, 1.0f);

                    Effect.PushMatrix(Light.GetFullTransform());
                    Effect.PushMatrix(Matrix.CreateTranslation(new Vector3(Light.Position, 0.0f)));
                    Effect.PushMatrix(Matrix.CreateScale(Light.Radius * 2));

                    Pusher.DrawSprite(false);

                    Effect.PopMatrix();
                    Effect.PopMatrix();
                    Effect.PopMatrix();
                }
            }

            Device.SetRenderTarget(null);
            Device.Viewport = Viewport;
            Effect.ProjectionMatrix = Cam.Projection;
            Effect.ViewMatrix = Cam.View;
            Effect.Color = Vector4.One;

            Effect.LightMap = BackgroundLightBuffer;

            Device.Clear(Microsoft.Xna.Framework.Color.Black);
            Effect.Technique = "DrawLighted";
            if (!String.IsNullOrEmpty(Header.BackgroundTexture))
            {
                Effect.Texture = Content.GetTexture(Header.BackgroundTexture);
                Pusher.DrawFullScreenQuad(Header.BackgroundScale, Cam);
            }

            DrawLayer(Elements, Effect, Pusher, Content, Data.EntityRecord._DrawDepth.BackGround);

            Effect.LightMap = MiddlegroundLightBuffer;
            
            DrawLayer(Elements, Effect, Pusher, Content, Data.EntityRecord._DrawDepth.MiddleGround);
            
            Effect.Technique = "DrawFullbright";
            DrawLayer(Elements, Effect, Pusher, Content, Data.EntityRecord._DrawDepth.ForeGround);
        }

        public void DrawSimpleMap(Jemgine.Data.MapData Map, GraphicsDevice Device, Camera Cam,
            RenderEffect Effect, VertexPusher Pusher, EpisodeContentManager Content)
        {
            Effect.ProjectionMatrix = Cam.Projection;
            Effect.ViewMatrix = Cam.View;
            Effect.Color = Vector4.One;

            Effect.LightMap = BackgroundLightBuffer;

            Effect.Technique = "DrawFullbright";
            DrawLayerEx(Map.Elements, Effect, Pusher, Content);
        }

        private void DrawLayer(List<Data.EntityRecord> Elements, RenderEffect Effect, VertexPusher Pusher, EpisodeContentManager Content,
            Data.EntityRecord._DrawDepth Layer)
        {
            foreach (var Entity in from Element in Elements where Element.DrawDepth == Layer select Element)
                DrawEntity(Effect, Pusher, Content, Entity);
        }

        private void DrawLayerEx(List<Data.MapElement> Elements, RenderEffect Effect, VertexPusher Pusher, 
            EpisodeContentManager Content)
        {
            foreach (var Entity in from Element in Elements where Element is Data.EntityRecord select Element)
                DrawEntity(Effect, Pusher, Content, Entity);
        }

        private void DrawEntity(RenderEffect Effect, VertexPusher Pusher, EpisodeContentManager Content, Data.MapElement A)
        {
            var E = A as Data.EntityRecord;
            Effect.PushMatrix(E.GetTransform());
            Effect.Tint = E.Tint;
            E.ForEachAttachedReverse((B) => DrawEntityElement(B, Effect, Pusher, Content));
            Effect.PopMatrix();
        }

        private void DrawEntityElement(Data.EntityElement Element,
            RenderEffect Effect, VertexPusher Pusher, EpisodeContentManager Content)
        {
            if (Element is IDrawable)
            {
                if (Element.BoneReference != null) Effect.PushMatrix(Element.BoneReference.Transform);
                (Element as IDrawable).Draw(Effect, Pusher, Content);
                if (Element.BoneReference != null) Effect.PopMatrix();
            }
        }
    }
}
